package main

import (
	"errors"
	"fmt"
	"io/fs"
	"log"
	"os"
	"path"
	"strings"
	"sync"
	"xliu.com/giteebackup/pkg"
	"xliu.com/giteebackup/pkg/config"
	"xliu.com/giteebackup/pkg/gitee"
	"xliu.com/giteebackup/pkg/shell"
)

func main() {
	conf := config.GetConfig()
	ignoreReposPrefix := conf.GetStringSlice("ignoreReposPrefix")
	token := conf.GetString("token")

	giteeRepo := gitee.NewGiteeRepo(token)

	allRepos, err := giteeRepo.GetAllRepos()
	if err != nil {
		panic(fmt.Errorf("get repos failed, %v", err))
	}

	// filter repos
	var repos []pkg.RepoInfo
	for _, item := range allRepos {
		var filter bool
		for _, prefix := range ignoreReposPrefix {
			if strings.HasPrefix(item.Name, prefix) {
				filter = true
				break
			}
		}
		if !filter {
			repos = append(repos, item)
		}
	}

	generateReadme(repos)

	// storePath := ""
	// backupRepo(repos, storePath)

}

func generateReadme(repos []pkg.RepoInfo) error {

	f, err := os.OpenFile("./README.md", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	f.WriteString("# 项目总览\n")

	format := "- [%s](%s): (%s) %s\n"
	for _, repo := range repos {
		repoLink := strings.TrimSuffix(repo.HtmlUrl, ".git")
		formats := fmt.Sprintf(format, repo.Name, repoLink, repo.LastCommitTime.Format("2006-01-02"), repo.Description)
		fmt.Println(formats)

		f.WriteString(formats)
	}
	return err
}

func backupRepo(repos []pkg.RepoInfo, storePath string) {
	// max concurrency is 3
	concurrency := 3
	queue := make(chan pkg.RepoInfo, concurrency)

	wg := sync.WaitGroup{}
	wg.Add(len(repos))

	go func() {
		for _, repo := range repos {
			queue <- repo
		}
	}()

	for i := 0; i < concurrency; i++ {
		go func(queue chan pkg.RepoInfo, wg *sync.WaitGroup, storePath string) {
			handleRepo(queue, wg, storePath)
		}(queue, &wg, storePath)
	}

	// wait for all repo update
	wg.Wait()

	// close the queue channel, make go routine break
	close(queue)
}

func handleRepo(queue chan pkg.RepoInfo, wg *sync.WaitGroup, storePath string) {
	// TODO: prevent panic causing for break
	for {
		// get a repo from queue
		repo, closed := <-queue

		if closed {
			return
		}

		wg.Done()

		exist, err := cloneRepoIfNotExist(repo, storePath)
		if err != nil {
			log.Println("ERROR: git clone repo", err)
			continue
		}

		// clone is the newest repo, no need to pull
		if !exist {
			continue
		}

		err = pullLatestCommit(repo, storePath)
		if err != nil {
			log.Println("ERROR: pull commit", err)
		}
	}
}

func pullLatestCommit(repo pkg.RepoInfo, basePath string) error {
	repoPath := path.Join(basePath, repo.Name)

	err := shell.Execute([]string{"cd", repoPath, ";", "git", "pull", ";", "cd", "-"})

	return err
}

func cloneRepoIfNotExist(repo pkg.RepoInfo, basePath string) (exist bool, err error) {
	repoPath := path.Join(basePath, repo.Name)

	stat, err := os.Stat(repoPath)

	// repo directory already exist
	if stat != nil && stat.IsDir() {
		return true, nil
	}

	// not file-not-exist error
	if err != nil && !errors.Is(err, fs.ErrNotExist) {
		return false, err
	}

	// file not exist, clone it to basePath, default name is
	err = shell.Execute([]string{"git", "clone", repo.HtmlUrl, repoPath})

	return
}
